本节代码对应 GitHub 分支: chapter6
# 构建路由
榜单详情页面在这里我们需要构建一个专门的路由,目前我们就以推荐歌单的数据来完成详情页开发。
首先在 routes/index.js 中,
import Album from '../application/Album';
// 在 /recommend 后面加上子路由
{
path: "/recommend",
component: Recommend,
routes: [
{
path: "/recommend/:id",
component: Album
}
]
},
然后在 component/list/index.js 中设置跳转:
const enterDetail = (id) => {
props.history.push (`/recommend/${id}`)
}
// 加入事件绑定逻辑
<ListItem key={item.id} onClick={() => enterDetail (item.id)}>
//...
注意,这里 List 组件作为 Recommend 的子组件,并不能从 props 拿到 history 变量,无法跳转路由。有两种解决方法:
- 将 Recommend 组件中 props 对象中的 history 属性传给 List 组件
- 将 List 组件用 withRouter 包裹
这里我们用第二种方式:
//List/index.js
import { withRouter } from 'react-router-dom';
// 省略组件代码
// 包裹
export default React.memo (withRouter (RecommendList));
这样,现在就能拿到 history 变量,顺利进行路由跳转了。
但是,Album 组件现在并没有编写。简单来写一下:
//src/application/Album/index.js
import React from 'react';
import {Container} from './style';
function Album (props) {
return (
<Container>
</Container>
)
}
export default Album;
在同目录下的 style.js:
import styled from'styled-components';
import style from '../../assets/global-style';
export const Container = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 100;
background: #fff;
`
现在你点击一个歌单,url 地址确实变化了,但页面却没有任何改变,这是什么原因呢?这里我卖个关子,给大家提供一个解决问题的思路。 第一反应是层叠上下文的问题吗?结果改变了 z-index 值,页面还是一样。说明并不是这个问题。
接着,这个组件究竟渲染了没?在 Album/index.js 的组件函数中,输出一些内容,到页面中,跳转后这些内容并未输出。此时,可以断定是组件没有渲染的问题。但是路由都改变了,配置也没错,怎么会出现这个问题呢?在这个时候,就考验我们对路由配置原理的理解了,具体来说就是 renderRoutes 方法。这个方法中传入参数为路由配置数组,我们在组件中调用这个方法后只能渲染一层路由,再深层的路由就无法渲染。
因此,我们现在在 Recommend 组件中加入这些逻辑即可:
有人说下面的props.route.routes有问题,是因为之前的子路由名称写成了 children 而不是 routes,这里默认配置项的子路由名字都是 routes
import { renderRoutes } from 'react-router-config';
// 返回的 JSX
<Content>
// 其他代码
// 将目前所在路由的下一层子路由加以渲染
{ renderRoutes (props.route.routes) }
</Content>
现在就有跳转效果了。
# 动画实现
本项目所有的过渡动画采用成熟的第三方库 react-transition-group。首先安装:
npm install react-transition-group --save
接下来我们来初步地使用:
//Album/index.js
import React, {useState} from 'react';
import {Container} from './style';
import { CSSTransition } from 'react-transition-group';
function Album (props) {
const [showStatus, setShowStatus] = useState (true);
return (
<CSSTransition
in={showStatus}
timeout={300}
classNames="fly"
appear={true}
unmountOnExit
onExited={props.history.goBack}
>
<Container>
</Container>
</CSSTransition>
)
}
export default React.memo (Album);
然后在相应的 style.js 中,
import styled from'styled-components';
import style from '../../assets/global-style';
export const Container = styled.div`
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1000;
background: ${style ["background-color"]};
transform-origin: right bottom;
&.fly-enter, &.fly-appear {
transform: translate3d (100%, 0, 0);
}
&.fly-enter-active, &.fly-appear-active {
transition: transform .3s;
transform: translate3d (0, 0, 0);
}
&.fly-exit {
transform: translate3d (0, 0, 0);
}
&.fly-exit-active {
transition: transform .3s;
transform: translate3d (100%, 0, 0);
}
`
现在你回到首页,然后点击一个歌单,你会看到一个滑入的动画。但是作为一个精致的项目,这个效果还不够,完整版的项目里面呈现的是一个切入的效果,那这个如何来实现?
也算是一个经验吧,这里直接分享给大家。需要把握两点:
- 设定 transfrom 的固定点,接下来的动画都是绕这个点旋转或平移
- 设置 rotateZ 的值,让整个页面能够拥有 Z 坐标方向的矢量
修改后如下:
// 动画样式代码
transform-origin: right bottom;
&.fly-enter, &.fly-appear {
transform: rotateZ (30deg) translate3d (100%, 0, 0);
}
&.fly-enter-active, &.fly-appear-active {
transition: transform .3s;
transform: rotateZ (0deg) translate3d (0, 0, 0);
}
&.fly-exit {
transform: rotateZ (0deg) translate3d (0, 0, 0);
}
&.fly-exit-active {
transition: transform .3s;
transform: rotateZ (30deg) translate3d (100%, 0, 0);
}
这个切入的动画就完成了。同样离开页面的时候,也有切出的动画。要检验整个效果,我们先来准备好路由的跳转。
# Header 基础组件开发
由于比较简单,就直接贴上 Header 组件的代码了。
//baseUI/header/index.js
import React from 'react';
import styled from'styled-components';
import style from '../../assets/global-style';
import PropTypes from "prop-types";
const HeaderContainer = styled.div`
position: fixed;
padding: 5px 10px;
padding-top: 0;
height: 40px;
width: 100%;
z-index: 100;
display: flex;
line-height: 40px;
color: ${style ["font-color-light"]};
.back {
margin-right: 5px;
font-size: 20px;
width: 20px;
}
>h1 {
font-size: ${style ["font-size-l"]};
font-weight: 700;
}
`
// 处理函数组件拿不到 ref 的问题,所以用 forwardRef
const Header = React.forwardRef ((props, ref) => {
const { handleClick, title} = props;
return (
<HeaderContainer ref={ref}>
<i className="iconfont back" onClick={handleClick}></i>
<h1>{title}</h1>
</HeaderContainer>
)
})
Header.defaultProps = {
handleClick: () => {},
title: "标题",
};
Header.propTypes = {
handleClick: PropTypes.func,
title: PropTypes.string,
};
export default React.memo (Header);
现在在 Album 组件中直接使用:
// 先引入
import Header from './../../baseUI/header/index';
const handleBack = () => {
setShowStatus (false);
};
//Container 组件下声明 Header
// 前面代码省略
<Header title={"返回"} handleClick={handleBack}></Header>
现在你就能看到返回的箭头和文字啦,虽然颜色比较淡,但点击能够正常的跳转并显示切出动画。那看到这里你不禁要问了,我们只是通过 setShowStatus 把状态置为了 false,让退出的动画执行一次,为什么会有路由跳转呢?
你可能忘了,在写 CSSTransition 的时候,我特意加上了这一句:
onExited={props.history.goBack}
什么意思?在退出动画执行结束时跳转路由。
那你可能会说,为什么不是直接在 handleBack 里面直接跳转路由呢?这里就是我踩过的一个坑,大家可以试试把 CSSTransition 中的 onExited 钩子删去,然后在 handleBack 中跳转路由。你会发现,动画根本就没有出现!
让我来给你解释一下这是为什么,当你点击后,执行路由跳转逻辑,这个时候路由变化,当前的组件会被立即卸载,相关的动画当然也就不复存在了。最后我的解决方案就是,先让页面切出动画执行一次,然后在动画执行完的瞬间跳转路由,这就达到我们的预期了,这也就是现在呈现给大家的方案。
OK,关于切页动画就分享到这里了,接下来我们开始核心页面的布局。
← 排行榜单模块开发 歌单详情2 准备静态模板 →